﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using CrmClient.QueryConvert;
using Microsoft.Xrm.Sdk.Query;

namespace CrmClient.Query
{
    public class CrmQueryable<T> : IEnumerable<T>
    {
        public string EntityName { get; private set; }

        public IEnumerator<T> GetEnumerator()
        {
            if (PropertiesMap == null)
                return QueryExecutor.ExecureQuery<T>(BuildQuery(), PropertiesMap, _pagedQueryInfo).GetEnumerator();
            return QueryExecutor.ExecureQuery<T>(BuildQuery(), PropertiesMap, _pagedQueryInfo).GetEnumerator();
        }
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        internal CrmClientBase QueryExecutor { get; private set; }
        internal PropertiesMap PropertiesMap { get; private set; }

        internal CrmQueryable(CrmClientBase queryExecutor, PropertiesMap propertiesMap, string entityName)
        {
            QueryExecutor = queryExecutor;
            EntityName = entityName;
            PropertiesMap = propertiesMap;
        }

        #region Clone

        internal virtual CrmQueryable<R> CreateInstance<R>(PropertiesMap propertiesMap, string entityName)
        {
            return new CrmQueryable<R>(QueryExecutor, propertiesMap, entityName);
        }
        internal CrmQueryable<R> Clone<R>(string entityName, PropertiesMap propertiesMap)
        {
            var res = CreateInstance<R>(propertiesMap, entityName);
            CloneContent(res);
            return res;
        }

        protected void CloneContent<R>(CrmQueryable<R> query)
        {
            if (_filter != null)
                query.AddFilter(CloneFilter(_filter));
            if (_sortOrders != null)
                foreach (var sortOrder in _sortOrders)
                    query.AddSortOrder(sortOrder);
            query.SetDistinct(_distinct);
            query.SetNoLock(_noLock);
        }
        private FilterExpression CloneFilter(FilterExpression filter)
        {
            var res = new FilterExpression();
            if (filter.Conditions != null)
                foreach (var condition in filter.Conditions)
                    res.Conditions.Add(CloneCondition(condition));
            if (filter.Filters != null)
                foreach (var childfilter in filter.Filters)
                    res.Filters.Add(CloneFilter(childfilter));
            return res;
        }
        private ConditionExpression CloneCondition(ConditionExpression condition)
        {
            return new ConditionExpression(condition.AttributeName, condition.Operator, condition.Values);
        }

        #endregion

        #region Filter

        private FilterExpression _filter;
        internal FilterExpression Filter
        {
            get { return _filter; }
        }
        internal void AddFilter(FilterExpression filter)
        {
            if (_filter == null)
                _filter = filter;
            else
                _filter.AddFilter(filter);
        }

        #endregion

        #region SortOrder

        private List<OrderExpression> _sortOrders;
        internal void AddSortOrder(OrderExpression sortOrder)
        {
            if (_sortOrders == null)
                _sortOrders = new List<OrderExpression>();
            _sortOrders.Add(sortOrder);
        }

        #endregion

        #region Paging

        private PagedQueryInfo _pagedQueryInfo;
        private PagingInfo _pagingInfo;
        public List<T> CrmGetPage(int pageNumber, int pageSize, out int totalCount, out bool moreRecordsExists)
        {
            _pagingInfo = new PagingInfo();
            _pagingInfo.Count = pageSize;
            _pagingInfo.PageNumber = pageNumber;
            _pagingInfo.ReturnTotalRecordCount = true;

            _pagedQueryInfo = new PagedQueryInfo();
            var res = this.ToList();
            totalCount = _pagedQueryInfo.TotalRecordCount;
            moreRecordsExists = _pagedQueryInfo.MoreRecords;
            return res;
        }

        #endregion

        #region Distinct

        private bool _distinct;
        internal void SetDistinct(bool distinct)
        {
            _distinct = distinct;
        }

        #endregion

        #region NoLock

        private bool _noLock;
        internal void SetNoLock(bool nolock)
        {
            _noLock = nolock;
        }

        #endregion

        #region QueryBuilder

        protected virtual void SetQueryColumnSet(QueryExpression query)
        {
            query.ColumnSet = new ColumnSet(PropertiesMap.Where(i => i.IsMappedToCrmEntity).Select(i => i.CrmAttr.ToString()).Distinct().ToArray());
        }
        protected virtual QueryExpression BuildQuery()
        {
            var res = new QueryExpression(EntityName);
            SetQueryColumnSet(res);

            if (_filter != null)
                res.Criteria = _filter;

            if (_sortOrders != null)
                foreach (var sortOrder in _sortOrders)
                    res.Orders.Add(sortOrder);

            if (_pagingInfo != null)
                res.PageInfo = _pagingInfo;

            res.NoLock = _noLock;
            res.Distinct = _distinct;

            return res;
        }

        #endregion

        #region Converters

        internal virtual CrmJoinedQueryable<R> ConvertToJoin<R>(string toEntityName, Func<CrmLinkAlias, PropertiesMap> propertiesMapGetter, out CrmLinkAlias alias)
        {
            alias = new CrmLinkAlias(1, toEntityName);
            var propertiesMap = propertiesMapGetter(alias);
            var res = new CrmJoinedQueryable<R>(QueryExecutor, propertiesMap, toEntityName);
            CloneContent(res);
            return res;
        }

        #endregion
    }
}
